#include "sdk.h"
#include <wx/bitmap.h>
#include <wx/dc.h>
#include <wx/dcbuffer.h>
#include "byosnake.h"
#include "byogame.h"

BEGIN_EVENT_TABLE( Snake, GameBase )
  EVT_KEY_DOWN( Snake::OnKeyDown )
  EVT_PAINT( Snake::OnPaint )
  EVT_TIMER( -1, Snake::OnTimer )
  EVT_ERASE_BACKGROUND( Snake::OnEraseBack )
END_EVENT_TABLE()

Snake::Snake( wxWindow* parent, const wxString& GameName ):
  GameBase( parent, GameName ),
  m_AppleX( 0 ),
  m_AppleY( 0 ),
  m_SnakeLen( 4 ),
  m_Delay( 250 ),
  m_Lives( 3 ),
  m_Score( 0 ),
  m_InitialSlowdownCnt( 0 ),
  m_KillCnt( 0 ),
  m_Font( GetFont() ),
  m_Timer( this, -1 ),
  m_Direction( dDown ) {
  RecalculateSizeHints( m_FieldHoriz + 2, m_FieldVert + 4 );
  InitializeSnake();
  RandomizeApple();
  StartSnake();
}

Snake::~Snake() {
}

void Snake::OnKeyDown( wxKeyEvent& event ) {
  if( event.GetKeyCode() == 'p' || event.GetKeyCode() == 'P' ) {
    SetPause( !IsPaused() );
    Refresh();
  }
  if( IsPaused() ) {
    return;
  }
  if( event.GetKeyCode() == WXK_LEFT ) {
    m_Direction = dLeft;
    Move();
  }
  if( event.GetKeyCode() == WXK_RIGHT ) {
    m_Direction = dRight;
    Move();
  }
  if( event.GetKeyCode() == WXK_UP ) {
    m_Direction = dUp;
    Move();
  }
  if( event.GetKeyCode() == WXK_DOWN ) {
    m_Direction = dDown;
    Move();
  }
}

void Snake::OnPaint( wxPaintEvent& ) {
  wxSize size = GetClientSize();
  wxBitmap buffer( wxImage( size.GetWidth(), size.GetHeight() ) );
  wxBufferedPaintDC DC( this, buffer );
  DrawBorder( &DC );
  DrawSnake( &DC );
  DrawApple( &DC );
  DrawStats( &DC );
}

void Snake::OnEraseBack( wxEraseEvent& ) {
}

void Snake::OnTimer( wxTimerEvent& ) {
  Move();
}

void Snake::InitializeSnake() {
  for( int i = 0; i < m_SnakeLen; i++ ) {
    m_SnakeX[i] = m_FieldHoriz / 2;
    m_SnakeY[i] = 0;
  }
  m_InitialSlowdownCnt = 2;
  m_Direction = dDown;
  RebuildField();
  UpdateSpeed();
}

void Snake::RandomizeApple() {
  if( m_SnakeLen == m_FieldTotal ) {
    m_AppleX = -1;
    m_AppleY = -1;
    return;
  }
  int randRange = m_FieldTotal - m_SnakeLen;
  int randNum = ( ( int )( ( float )rand() * ( float )randRange / ( float )RAND_MAX ) ) % randRange;
  m_AppleX = 0;
  m_AppleY = 0;
  while( randNum-- > 0 ) {
    do {
      m_AppleX++;
      if( m_AppleX >= m_FieldHoriz ) {
        m_AppleX = 0;
        m_AppleY++;
        if( m_AppleY >= m_FieldVert ) {
          m_AppleX = -1;
          m_AppleY = -1;
          return;
        }
      }
    } while( m_Field[m_AppleX][m_AppleY] );
  }
}

void Snake::StartSnake() {
  m_Timer.Start( m_Delay, true );
  m_KillCnt = 0;
}

void Snake::RebuildField() {
  memset( m_Field, 0, sizeof( m_Field ) );
  for( int i = 0; i < m_SnakeLen; i++ ) {
    m_Field[m_SnakeX[i]][m_SnakeY[i]] = true;
  }
}

void Snake::Move() {
  if( IsPaused() ) {
    Refresh();
    m_Timer.Start( -1, true );
    return;
  }
  if( m_InitialSlowdownCnt ) {
    m_InitialSlowdownCnt--;
    m_Timer.Start( -1, true );
    return;
  }
  int newX = m_SnakeX[0];
  int newY = m_SnakeY[0];
  switch( m_Direction ) {
    case dLeft:
      newX--;
      break;
    case dRight:
      newX++;
      break;
    case dUp:
      newY--;
      break;
    case dDown:
      newY++;
      break;
    default:
      break;
  }
  bool valid = true;
  if( newX < 0 || newX >= m_FieldHoriz ) {
    valid = false;
  }
  if( newY < 0 || newY >= m_FieldVert ) {
    valid = false;
  }
  for( int i = 0; valid && i < m_SnakeLen - 1; i++ )
    if( m_SnakeX[i] == newX && m_SnakeY[i] == newY ) {
      valid = false;
    }
  if( !valid ) {
    if( ++m_KillCnt >= m_MaxKillCnt ) {
      Died();
    } else {
      m_Timer.Start( -1, true );
    }
    Refresh();
    return;
  }
  m_KillCnt = 0;
  if( newX == m_AppleX && newY == m_AppleY ) {
    GetsBigger();
  }
  for( int i = m_SnakeLen; i-- > 0; ) {
    m_SnakeX[i] = m_SnakeX[i - 1];
    m_SnakeY[i] = m_SnakeY[i - 1];
  }
  m_SnakeX[0] = newX;
  m_SnakeY[0] = newY;
  RebuildField();
  if( newX == m_AppleX && newY == m_AppleY ) {
    RandomizeApple();
  } else {
    m_Score -= m_Delay / 10;
    if( m_Score < 0 ) {
      m_Score = 0;
    }
  }
  Refresh();
  m_Timer.Start( -1, true );
}

void Snake::Died() {
  if( --m_Lives == 0 ) {
    Refresh();
    GameOver();
    return;
  }
  InitializeSnake();
  RandomizeApple();
  StartSnake();
}

void Snake::GameOver() {
  wxMessageBox( _( "Game over." ) );
}

void Snake::DrawBorder( wxDC* DC ) {
  for( int i = 0; i < m_FieldHoriz + 2; i++ ) {
    DrawBrick( DC, i, 2, GetColour( m_BorderColour ) );
    DrawBrick( DC, i, 3 + m_FieldVert, GetColour( m_BorderColour ) );
  }
  for( int i = 0; i < m_FieldVert; i++ ) {
    DrawBrick( DC, 0, i + 3, GetColour( m_BorderColour ) );
    DrawBrick( DC, m_FieldHoriz + 1, i + 3, GetColour( m_BorderColour ) );
  }
}

void Snake::DrawSnake( wxDC* DC ) {
  for( int i = 0; i < m_SnakeLen; i++ ) {
    DrawBrick( DC, m_SnakeX[i] + 1, m_SnakeY[i] + 3, GetColour( m_SnakeColour ) );
  }
}

void Snake::DrawApple( wxDC* DC ) {
  if( m_AppleX >= 0 && m_AppleY >= 0 ) {
    DrawBrick( DC, m_AppleX + 1, m_AppleY + 3, GetColour( m_AppleColour ) );
  }
}

void Snake::DrawStats( wxDC* DC ) {
  DC->SetTextForeground( *wxWHITE );
  DC->SetTextBackground( *wxBLACK );
  DC->SetFont( m_Font );
  wxString Line1 = wxString::Format( _( "Lives: %d    Score: %d   Length: %d" ), m_Lives, m_Score, m_SnakeLen );
  wxString Line2 = IsPaused() ? _( "Paused" ) : wxEmptyString;
  wxString Line3 = GetBackToWorkString();
  DC->DrawText( Line1, 5, 5 );
  int xs, ys;
  DC->GetTextExtent( Line1, &xs, &ys );
  DC->DrawText( Line2, 5, 5 + 2 * ys );
  DC->DrawText( Line3, 5, 5 + 4 * ys );
}

void Snake::GetsBigger() {
  m_SnakeLen++;
  m_Score += 1000;
  UpdateSpeed();
}

void Snake::UpdateSpeed() {
  int level = ( m_SnakeLen / 10 ) + 1;
  if( level > 11 ) {
    level = 11;
  }
  m_Delay = 250 - 20 * level;
  m_Timer.Start( m_Delay, true );
}

BYO_REGISTER_GAME( Snake, "Snake" )
